use super::{
    err, err_value, json, throw_err, Base, Client, ECode, MapSv, RNull, Resp, TBase, VBase, VMap,
    VSend, Value, SS,
};
use base::{
    pre_cur_day,
    tbase::{get_cfg, save_cfg, to_m},
};
use std::collections::HashMap;
use tracing::{instrument, warn};

pub struct Ground;

impl Base for Ground {}
impl TBase for Ground {}

impl Ground {
    #[instrument(skip_all, name = "load_db")]
    pub async fn load_db(&self, line: &str, client: &Client, tx: &VSend) -> RNull {
        warn!("load_db");

        let mut cfg = SS::new();

        let _ = get_cfg("pairpos", line, &mut cfg, client).await;

        let tab = vec![
            ("outdia", "dia"),
            ("outdt", "dt"),
            ("outwidth", "width"),
            ("india", "dia"),
            ("indt", "dt"),
            ("inwidth", "width"),
        ];

        for (d, key) in tab.iter() {
            let tp = if d.starts_with("in") {
                "inner"
            } else if d.starts_with("out") {
                "outer"
            } else {
                continue;
            };

            if let Ok(body) = self.read_db(line, d, &cfg, client).await {
                let body = body.ck_obj("data")?.ck_obj(d)?;

                let end = body.ck_str("end")?;
                let data = body.ck_array("data")?;

                cfg.insert(d.to_string(), end.to_string());

                for val in data.iter() {
                    let time = match val.k_str("time") {
                        Some(v) => v,
                        None => {
                            continue;
                        }
                    };
                    let psn = match val.k_str("psn") {
                        Some(v) => v,
                        None => {
                            continue;
                        }
                    };
                    let tsn = match val.k_str("tsn") {
                        Some(v) => v,
                        None => {
                            continue;
                        }
                    };
                    let val = match val.k_f64("val") {
                        Some(v) => v,
                        None => {
                            continue;
                        }
                    };

                    let cmd = json!({
                        "cmd": "upval",
                        "line": line,
                        "sn": psn,
                        "tp": tp,
                        "tm": time,
                        "tsn": tsn,
                        "k": key,
                        "v": val,
                    });

                    let _ = tx.send(cmd).await;
                }
            }
        }

        let _ = save_cfg("pairpos", line, &cfg, client).await;

        Ok(())
    }

    #[instrument(skip_all, name = "update_val")]
    pub async fn update_val(&self, data: &MapSv, client: &Client) -> RNull {
        warn!("buffer update_val");

        let line = data.ck_str("line")?;
        let sn = data.ck_str("sn")?;
        let tp = data.ck_str("tp")?;
        let tm = data.ck_str("tm")?;
        let tsn = data.ck_str("tsn")?;
        let key = data.ck_str("k")?;
        let val = data.ck_val("v")?;

        let cmd = json!({ "line": line, "k": sn });
        let body = client.cfgdb(&cmd, &format!("{tp}/get")).await?;
        let body = body.ck_obj("data")?;

        let tag = self.get_tag(key);
        if let Some(t) = body.k_str(&tag) {
            if t > tm {
                return Ok(());
            }
        }

        let mut ret = MapSv::new();

        ret.insert("sn".to_string(), json!(sn));
        ret.insert("tech".to_string(), json!(tsn));
        ret.insert(key.to_string(), json!(val));
        ret.insert(tag, json!(tm));

        let _ = self.calc_gc(line, tp, &mut ret, client).await;

        let cmd = json!({
            "line": line,
            "v": ret
        });

        client.cfgdb(&cmd, &format!("{tp}/mditem")).await?;

        Ok(())
    }

    #[instrument(skip_all, name = "update")]
    pub async fn update(&self, data: &MapSv, client: &Client) -> RNull {
        warn!("buffer update: start");

        let line = data.ck_str("line")?;
        let sn = data.ck_str("sn")?;
        let tp = data.ck_str("tp")?;

        warn!("buffer update: getdata");

        let ret = self.get_data(line, sn, tp, client).await?;

        warn!("buffer update: {}", ret.len());

        if ret.is_empty() {
            return Ok(());
        }

        let cmd = json!({ "line": line, "k": sn });
        let body = client.cfgdb(&cmd, &format!("{tp}/get")).await?;
        let body = body.ck_obj("data")?;

        let key = vec!["dia", "dt", "width"];

        let mut val = HashMap::new();

        if let None = body.k_str_empty_trim("tech") {
            if let Some(v) = ret.k_str_empty_trim("tech") {
                val.insert("tech".to_string(), json!(v));
            }
        }

        for k in key.iter() {
            let tag = self.get_tag(k);

            let tmp_v = match ret.k_val(k) {
                Some(v) => v.clone(),
                None => {
                    continue;
                }
            };

            let tm = match ret.k_str(&tag) {
                Some(v) => v,
                None => {
                    val.insert(k.to_string(), tmp_v);
                    continue;
                }
            };

            let old_tm = match body.k_str(&tag) {
                Some(v) => v,
                None => {
                    val.insert(k.to_string(), tmp_v);
                    val.insert(tag.to_string(), json!(tm));
                    continue;
                }
            };

            if old_tm >= tm {
                continue;
            }

            val.insert(k.to_string(), tmp_v);
            val.insert(tag.to_string(), json!(tm));
        }

        if val.is_empty() {
            return Ok(());
        }

        val.insert("sn".to_string(), json!(sn));

        let cmd = json!({
            "line": line,
            "v": val
        });

        warn!("buffer update: mditem");

        client.cfgdb(&cmd, &format!("{tp}/mditem")).await?;

        Ok(())
    }

    async fn get_data(
        &self,
        line: &str,
        sn: &str,
        tp: &str,
        client: &Client,
    ) -> Result<MapSv, Value> {
        let (tab, autotab) = match tp {
            "inner" => (
                vec![("inwidth", "width"), ("india", "dia"), ("indt", "dt")],
                "autoin",
            ),
            "outer" => (
                vec![("outwidth", "width"), ("outdia", "dia"), ("outdt", "dt")],
                "autout",
            ),
            _ => {
                throw_err!(ECode::Para, "box type error");
            }
        };

        let mut ret = MapSv::new();

        for (k, v) in tab.iter() {
            let _ = self.load_data(line, sn, k, v, &mut ret, client).await;
        }

        let _ = self.calc_gc(line, tp, &mut ret, client).await;

        let _ = self.load_auto(line, sn, autotab, &mut ret, client).await;

        Ok(ret)
    }

    async fn load_data(
        &self,
        line: &str,
        sn: &str,
        tab: &str,
        key: &str,
        ret: &mut MapSv,
        client: &Client,
    ) -> RNull {
        let tm = pre_cur_day(60, "%Y-%m-%dT%H:%M:%SZ")?;

        let cmd = json!({
            "n": tab,
            "obj": "*",
            "opt": {
                "desc": true,
                "count": 1,
                "and": [
                {"eq": "line", "v": line},
                {"eq": "psn", "v": sn},
                {"gt": "time", "v": tm},
                ]
            }
        });

        let body = client.timedb_nolog(&cmd, "get").await?;
        let body = body.ck_obj("data")?.ck_obj(tab)?.ck_first_obj("data")?;

        let time = body.ck_str("time")?;
        let tech = body.ck_str("tsn")?;
        let val = body.ck_f64("val")?;

        if let None = ret.k_str("tech") {
            ret.insert("tech".to_string(), json!(tech));
        }

        ret.insert(key.to_string(), json!(val));
        ret.insert(self.get_tag(key), json!(time));

        Ok(())
    }

    async fn calc_gc(&self, line: &str, tp: &str, ret: &mut MapSv, client: &Client) -> RNull {
        let cmd = json!({
            "line": line,
            "k": ret.ck_str("tech")?,
        });

        let body = client.cfgdb(&cmd, "tech/get").await?;
        let body = body.ck_obj("data")?;

        let (b_width, b_dia, b_dt) = match tp {
            "inner" => {
                let width = body.ck_f64("inwidth")?;
                let dia = body.ck_f64("india")?;
                let dt = body.ck_f64("indt")?;

                (width, dia, dt)
            }
            "outer" => {
                let width = body.ck_f64("outwidth")?;
                let dia = body.ck_f64("outdia")?;
                let dt = body.ck_f64("outdt")?;

                (width, dia, dt)
            }
            _ => {
                throw_err!(ECode::Para, "box type error");
            }
        };

        if let Some(d) = ret.k_f64("width") {
            ret.insert("width".to_string(), json!(to_m(d - b_width)));
        }

        if let Some(d) = ret.k_f64("dia") {
            ret.insert("dia".to_string(), json!(to_m(d - b_dia)));
        }

        if let Some(d) = ret.k_f64("dt") {
            ret.insert("dt".to_string(), json!(to_m(d - b_dt)));
        }

        Ok(())
    }

    async fn load_auto(
        &self,
        line: &str,
        sn: &str,
        tab: &str,
        ret: &mut MapSv,
        client: &Client,
    ) -> RNull {
        let tm = pre_cur_day(60, "%Y-%m-%dT%H:%M:%SZ")?;

        let cmd = json!({
            "n": tab,
            "obj": "*",
            "opt": {
                "desc": true,
                "count": 1,
                "and": [
                {"eq": "line", "v": line},
                {"eq": "psn", "v": sn},
                {"gt": "time", "v": tm},
                ]
            }
        });

        let body = client.timedb_nolog(&cmd, "get").await?;
        let body = body.ck_obj("data")?.ck_obj(tab)?.ck_first("data")?;

        let time = body.ck_str("time")?;
        let tech = body.ck_str("tsn")?;

        if let None = ret.k_str("tech") {
            ret.insert("tech".to_string(), json!(tech));
        }

        let k_val = vec!["width", "dia", "dt"];

        for k in k_val.iter() {
            let val = body.ck_f64(k)?;
            let tag = self.get_tag(k);

            let b = match ret.k_str(&tag) {
                Some(v) => v < time,
                None => true,
            };

            if b {
                ret.insert(k.to_string(), json!(val));
                ret.insert(tag, json!(time));
            }
        }

        Ok(())
    }

    async fn read_db(&self, line: &str, tab: &str, cfg: &SS, client: &Client) -> Resp {
        // 1 minutes
        let now = self.pre_cur_time(1000 * 60 * 1, "%Y-%m-%dT%H:%M:%SZ")?;
        let utc = self.utc_offset_from_zero();

        let mut opt = json!([
            {"eq": "line", "v": line},
            {"le": "time", "v": now, "utc": utc},
        ]);
        let opt = opt.ckarray_mut()?;

        if let Some(t) = cfg.k_str(tab) {
            opt.push(json!({"gt": "time", "v": t}));
        }

        let cmd = json!({
            "n": tab,
            "obj": "*",
            "opt": {
                "and": opt
            }
        });

        client.timedb_nolog(&cmd, "get").await
    }

    fn get_tag(&self, k: &str) -> String {
        format!("tm{k}")
    }
}
